from typing import (Any, Union, Type)  # noqa: F401

from eth_keys.datatypes import (
    BaseSignature,
    LazyBackend,
    NonRecoverableSignature,
    PublicKey,
    PrivateKey,
    Signature,
)
from eth_keys.exceptions import (
    ValidationError,
)
from eth_keys.validation import (
    validate_message_hash,
    validate_compressed_public_key_bytes,
    validate_uncompressed_public_key_bytes,
)


# These must be aliased due to a scoping issue in mypy
# https://github.com/python/mypy/issues/1775
_PublicKey = PublicKey
_PrivateKey = PrivateKey
_Signature = Signature
_NonRecoverableSignature = NonRecoverableSignature


class KeyAPI(LazyBackend):
    #
    # datatype shortcuts
    #
    PublicKey = PublicKey  # type: Type[_PublicKey]
    PrivateKey = PrivateKey  # type: Type[_PrivateKey]
    Signature = Signature  # type: Type[_Signature]
    NonRecoverableSignature = NonRecoverableSignature  # type: Type[_NonRecoverableSignature]

    #
    # Proxy method calls to the backends
    #
    def ecdsa_sign(self,
                   message_hash: bytes,
                   private_key: _PrivateKey) -> _Signature:
        validate_message_hash(message_hash)
        if not isinstance(private_key, PrivateKey):
            raise ValidationError(
                "The `private_key` must be an instance of `eth_keys.datatypes.PrivateKey`"
            )
        signature = self.backend.ecdsa_sign(message_hash, private_key)
        if not isinstance(signature, Signature):
            raise ValidationError(
                "Backend returned an invalid signature.  Return value must be "
                "an instance of `eth_keys.datatypes.Signature`"
            )
        return signature

    def ecdsa_sign_non_recoverable(self,
                                   message_hash: bytes,
                                   private_key: _PrivateKey) -> _NonRecoverableSignature:
        validate_message_hash(message_hash)
        if not isinstance(private_key, PrivateKey):
            raise ValidationError(
                "The `private_key` must be an instance of `eth_keys.datatypes.PrivateKey`"
            )
        signature = self.backend.ecdsa_sign_non_recoverable(message_hash, private_key)
        if not isinstance(signature, NonRecoverableSignature):
            raise ValidationError(
                "Backend returned an invalid signature.  Return value must be "
                "an instance of `eth_keys.datatypes.Signature`"
            )
        return signature

    def ecdsa_verify(self,
                     message_hash: bytes,
                     signature: BaseSignature,
                     public_key: _PublicKey) -> bool:
        validate_message_hash(message_hash)
        if not isinstance(public_key, PublicKey):
            raise ValidationError(
                "The `public_key` must be an instance of `eth_keys.datatypes.PublicKey`"
            )
        if not isinstance(signature, BaseSignature):
            raise ValidationError(
                "The `signature` must be an instance of `eth_keys.datatypes.BaseSignature`"
            )
        return self.backend.ecdsa_verify(message_hash, signature, public_key)

    def ecdsa_recover(self,
                      message_hash: bytes,
                      signature: _Signature) -> _PublicKey:
        validate_message_hash(message_hash)
        if not isinstance(signature, Signature):
            raise ValidationError(
                "The `signature` must be an instance of `eth_keys.datatypes.Signature`"
            )
        public_key = self.backend.ecdsa_recover(message_hash, signature)
        if not isinstance(public_key, _PublicKey):
            raise ValidationError(
                "Backend returned an invalid public_key.  Return value must be "
                "an instance of `eth_keys.datatypes.PublicKey`"
            )
        return public_key

    def private_key_to_public_key(self, private_key: _PrivateKey) -> _PublicKey:
        if not isinstance(private_key, PrivateKey):
            raise ValidationError(
                "The `private_key` must be an instance of `eth_keys.datatypes.PrivateKey`"
            )
        public_key = self.backend.private_key_to_public_key(private_key)
        if not isinstance(public_key, PublicKey):
            raise ValidationError(
                "Backend returned an invalid public_key.  Return value must be "
                "an instance of `eth_keys.datatypes.PublicKey`"
            )
        return public_key


# This creates an easy to import backend which will lazily fetch whatever
# backend has been configured at runtime (as opposed to import or instantiation time).
lazy_key_api = KeyAPI(backend=None)
